<?php
/**
 * @Copyright (c) 2011, 新浪网运营部-网络应用开发部
 * All rights reserved.
 * 数据操作基类
 * @author          wangxin <wangxin3@staff.sina.com.cn>
 * @time            2011/3/2 11:48
 * @version         Id: 0.9
*/

/**
* 数据库基类
*/
class BaseModelDB
{
    protected $DBConfig = array();    //指定数据库配置

    protected $DBName;              //库名称

    protected $link = null;          //库连接

    protected $sql;                  //最后一次执行的SQL

    protected $countNum;                  //统计结果数

    protected $errorCode = 0;   	//错误码

    protected $errorInfo = array();	//错误信息

    protected $debug = null;         //DEBUG模式
    
    protected $errorMod = null;     //报错模式

    protected $disableField = array();//禁止更改字段

    protected $ignoreErrorArr = array();//或略错误,此错误不报

    protected $tableName;   	    //数据表名称
    
    public $pageModel;           //翻页对象

    public $memcache;    //memcache缓存对象
    
    static private $reConnectErrorArr = array(2006,1317,2013,1046);  //重连错误

    /**
     * @param int $db_id    数据库序号（默认为0）
     */
    public function __construct($DBName = '', $DBConfig = array())
    {
        $this->DBName = $DBName;
        $this->DBConfig = $DBConfig;
    }

    public function setTableName($tableName)
    {
        $this->tableName = $tableName;
    }

    public function getTableName()
    {
        return $this->tableName;
    }

    public function setPageStyle($pageStyle)
    {
        is_object($this->pageModel) ? $this->pageModel->setStyle($pageStyle) : '';
    }

    public function setDisableField(array $disable_field)
    {
        $this->disableField = $disable_field;
    }

    public function getCountNum()
    {
        return $this->countNum;
    }

    public function getPageStr()
    {
        return is_object($this->pageModel) ? $this->pageModel->getPageStr() : '';
    }

    public function getPageJump()
    {
        return is_object($this->pageModel) ? $this->pageModel->getPageJump() : '';
    }

    public function getErrorInfo()
    {
        return $this->errorInfo;
    }

    /**
     * 执行SQL 返回二维数组
     */
    public function getData($sql, $data= array(), $pageSize = 0, $master_or_slave = "slave")
    {
        if (!is_array($data) && !is_numeric($pageSize))
        {
            $pageSize = $data;
            $data = array();
        }
        $this->checkLink($master_or_slave);
        if (is_numeric($pageSize) && $pageSize > 0)
        {
    		//获取读出记录数（用于翻页计算）
    		$pos = stripos($sql,"from");
    		$count_sql = "SELECT count(*) AS num ".substr($sql,$pos);
    		$count_sql = preg_replace("/[\s*]ORDER[\s*]BY.*/i","",$count_sql);
            $this->setSql($count_sql, $data);
            $error = 0;
            do {
                if ($error == 1) {
                    $this->checkLink($master_or_slave, true); 
                }
        		$query = $this->link->query($this->sql);
                $this->errorCode = $this->link->errno;
                if (in_array($this->errorCode, self::$reConnectErrorArr)) {
                    $error++;
                }  elseif ($this->errorCode !== 0) {
                    $this->errorInfo = $this->link->error;
                    $this->error("执行查询出错\nSQL:".$this->sql."\nMYSQL错误代码:".$this->errorCode."\n错误信息:".$this->errorInfo);
                } elseif ($error == 1) {
                    $error++;
                }
            } while ($error == 1);
            $numArr = $query->fetch_array(MYSQLI_ASSOC);
            BaseModelCommon::debug($numArr, "Result", 1);
            $this->countNum = $numArr['num'];
    		$this->pageModel = new BaseModelPage($this->countNum, $pageSize);
    		$sql .= $this->pageModel->getLimit();
        }

        $this->setSql($sql, $data);
		$error = 0;
        do {
            if ($error == 1) {
                $this->checkLink($master_or_slave, true); 
            }
		    $query = $this->link->query($this->sql);
            $this->errorCode = $this->link->errno;
            if (in_array($this->errorCode, self::$reConnectErrorArr)) {
                $error++;
            } elseif ($this->errorCode !== 0) {
                $this->errorInfo = $this->link->error;
                $this->error("执行查询出错\nSQL:".$this->sql."\nMYSQL错误代码:".$this->errorCode."\n错误信息:".$this->errorInfo);
            } elseif ($error == 1) {
                $error++;
            }
        } while ($error == 1);
        
        while($row = $query->fetch_array(MYSQLI_ASSOC)) {
            $arr[] = $row;
        }
        BaseModelCommon::debug($arr, "Result", 1);
        return $arr;
    }

    /**
     * 执行SQL 返回二维数组
     */
    public function getRow($sql, $data = array(), $master_or_slave = "slave")
    {
        $this->checkLink($master_or_slave);
        $this->setSql($sql, $data);
        $error = 0;
        do {
            if ($error == 1) {
                $this->checkLink($master_or_slave, true); 
            }
            $query = $this->link->query($this->sql);
            $this->errorCode = $this->link->errno;
            if (in_array($this->errorCode, self::$reConnectErrorArr)) {
                $error++;
            } elseif ($this->errorCode !== 0) {
                $this->errorInfo = $this->link->error;
                $this->error("执行查询出错\nSQL:".$this->sql."\nMYSQL错误代码:".$this->errorCode."\n错误信息:".$this->errorInfo);
            } elseif ($error == 1) {
                $error++;
            }
        } while ($error == 1);
		$arr = $query->fetch_array(MYSQLI_ASSOC);
        BaseModelCommon::debug($arr, "Result", 1);
        return $arr;
    }

    /**
     * 执行SQL 返回二维数组
     */
    public function getFirst($sql, $data= array(), $master_or_slave = "slave")
    {
        $this->checkLink($master_or_slave);
        $this->setSql($sql, $data);
		$error = 0;
        do {
            if ($error == 1) {
                $this->checkLink($master_or_slave, true); 
            }
            $query = $this->link->query($this->sql);
            $this->errorCode = $this->link->errno;
            if (in_array($this->errorCode, self::$reConnectErrorArr)) {
                $error++;
            } elseif ($this->errorCode !== 0) {
                $this->errorInfo = $this->link->error;
                $this->error("执行查询出错\nSQL:".$this->sql."\nMYSQL错误代码:".$this->errorCode."\n错误信息:".$this->errorInfo);
            } elseif ($error == 1) {
                $error++;
            }
        } while ($error == 1);
        $arr = $query->fetch_array(MYSQLI_NUM);
        BaseModelCommon::debug($arr[0], "Result", 1);
        return $arr[0];
	}

    /**
     * 获取插入数据id
     */
    public function insertId() {
        $sql = 'SELECT last_insert_id()';
        return $this->getFirst($sql, array(), 'master');
    }

    /**
     * 确保数据库连接
     * @param string $master_or_slave   检查主库还是从库
     * @return void
     * @author wangxin
     **/
    protected function checkLink($master_or_slave = "slave", $reConnect = false)
    {
		$this->link = BaseModelDBConnect::connectDB($this->DBName, $master_or_slave, $this->DBConfig, $reConnect);
    }

    public function getFields()
    {
        return array_keys($this->field_arr);
    }
    /**
     * 验证字段&&数据
     */
    protected function checkField($key, $value)
    {
        if (substr($key, 0, 1) == '#') {
            $key = substr($key, 1);
        }
        if (empty($this->field_arr[$key]))
        {
            $this->error("{$key}：字段不存在", array('field'=>$key, 'table'=>$this->tableName));
        }
        if (in_array($key, $this->disableField))
        {
            $this->error("{$key}：字段禁止修改", array('field'=>$key, 'table'=>$this->tableName));
        }
        $msg = Validate::check($value, $this->field_arr[$key]['type'], $this->field_arr[$key]['validate'], $this->field_arr[$key]['max_length']);
        if ($msg !== true)
        {
            $this->error("{$this->field_arr[$key]['name']}：" . $msg, array('field'=>$key, 'table'=>$this->tableName));
        }
        return true;
    }

    /**
      * data insert
      * @access  public
      * @param array $insert_arr
      * $insert_arr(
      * 	'key1'   => $value1,
      *		'key2'   => $value2,
      * 	.........
      * 	);
      * @param int delayed  false|true
      * @param array &$result
      * @return the number of this->_affected rows or false
      */
	public function insert($insert_value, $delayed = false, &$result = array())
	{
		if ($delayed === true)
		{
			$delayed = " DELAYED";
		}
		if(is_array($insert_value))
		{
			$this->checkLink("master");
			$all_key = $all_value = array();
			foreach ($insert_value as $key => $value)
			{
                $this->checkField($key, $value);
				$all_key[] = '`'.$key.'`';
				$all_value[] = "'".$this->link->real_escape_string($value)."'";
			}
			$this->setSql("INSERT{$delayed} INTO `".$this->getTableName()."` (".implode(',',$all_key).") VALUES (".implode(',',$all_value).")");
            $error = 0;
            do {
                if ($error == 1) {
                    $this->checkLink('master', true); 
                }
                $this->link->query($this->sql);
                $this->errorCode = $this->link->errno;
                $result['affected_num'] = $this->link->affected_rows;
                $this->errorCode = $this->link->errno;
                if (in_array($this->errorCode, self::$reConnectErrorArr)) {
                    $error++;
                } elseif ($this->errorCode !== 0) {
                    $this->errorInfo = $this->link->error;
                    $this->error("执行插入语句出错\nSQL:".$this->sql."\nMYSQL错误代码:".$this->errorCode."\n错误信息:".$this->errorInfo);
                } elseif ($error == 1) {
                    $error++;
                }
            } while ($error == 1);
            BaseModelCommon::debug("affected_num: " . $result['affected_num'], "Result", 1);
            return true;
		}
		else
		{
			return $this->error('插入数据有误');
		}
	}

    /**
      * data insert
      * @access  public
      * @param array $insert_arr
      * $insert_arr(
      * 	'key1'   => $value1,
      *		'key2'   => $value2,
      * 	.........
      * 	);
      * @param int delayed  false|true
      * @return true or false
      */
	public function replace($insert_value, $delayed = false, &$result = array())
	{
		if ($delayed === true)
		{
			$delayed = " DELAYED";
		}
		if(is_array($insert_value))
		{
			$this->checkLink("master");
			$all_key = $all_value = array();
			foreach ($insert_value as $key => $value)
			{
                $this->checkField($key, $value);
				$all_key[] = '`'.$key.'`';
				$all_value[] = "'".$this->link->real_escape_string($value)."'";
			}
			$this->setSql("REPLACE{$delayed} INTO `".$this->getTableName()."` (".implode(',',$all_key).") VALUES (".implode(',',$all_value).")");
            $error = 0;
            do {
                if ($error == 1) {
                    $this->checkLink('master', true); 
                }
                $this->link->query($this->sql);
                $this->errorCode = $this->link->errno;
                $result['affected_num'] = $this->link->affected_rows;
                $this->errorCode = $this->link->errno;
                if (in_array($this->errorCode, self::$reConnectErrorArr)) {
                    $error++;
                } elseif ($this->errorCode !== 0) {
                    $this->errorInfo = $this->link->error;
                    $this->error("执行REPLACE语句出错\nSQL:".$this->sql."\nMYSQL错误代码:".$this->errorCode."\n错误信息:".$this->errorInfo);
                } elseif ($error == 1) {
                    $error++;
                }
            } while ($error == 1);
            BaseModelCommon::debug("affected_num: " . $result['affected_num'], "Result", 1);
            return true;
		}
		else
		{
			return $this->error("REPLACE数据有误");
		}
	}

    /**
    * data update
    * @access  public
    * @param array $up_arr
    * $update_value(
    * 	'key1'   => $value1,
    *	'key2'   => $value2,
    * 	);
    * @param array||string $where
    *
    * @return true or false
    */
	public function update($update_value, $where, &$result = array())
	{
		if (is_array($update_value))
		{
			$this->checkLink("master");
            if (is_string($where))
            {
                $tmp_where = strtolower($where);
    			if (!strpos ($tmp_where, "=") && !strpos ($tmp_where, 'in') && !strpos ($tmp_where, 'like'))
    			{
				    $this->error("请检查条件");
    			}
            }
            elseif (is_array($where))
            {
                $tmp = array();//条件，对应key=value
    			foreach ($where as $key => $value)
    			{
    				$tmp[] = "`".$key."` = '".$this->link->real_escape_string($value)."'";
    			}
    			$where = implode(' AND ',$tmp);
            }
            else
            {
                $this->error("条件格式错误");
            }
			$all_up = array();//要更新字段的名称及对应的值
			foreach ($update_value as $key => $value)
			{
                $this->checkField($key, $value);
				if ($key{0} == "#")
				{
                    $key = substr($key, 1);
					$all_up[] = '`'.$key.'` = '.$this->link->real_escape_string($value);
				}
				else
				{
					$all_up[] = '`'.$key.'` = \''.$this->link->real_escape_string($value).'\'';
				}
			}
            if (empty($all_up))
            {
                $this->error("更新数据有误，数据不能为空！");
            }
			$this->setSql("UPDATE `".$this->getTableName()."` SET ".implode(',',$all_up)." WHERE " . $where);
			//echo $this->sql.'<hr />';
            $error = 0;
            do {
                if ($error == 1) {
                    $this->checkLink('master', true); 
                }
                $this->link->query($this->sql);
                $this->errorCode = $this->link->errno;
                $result['affected_num'] = $this->link->affected_rows;
                $this->errorCode = $this->link->errno;
                if (in_array($this->errorCode, self::$reConnectErrorArr)) {
                    $error++;
                } elseif ($this->errorCode !== 0) {
                    $this->errorInfo = $this->link->error;
                    $this->error("执行更新语句出错\nSQL:".$this->sql."\nMYSQL错误代码:".$this->errorCode."\n错误信息:".$this->errorInfo);
                } elseif ($error == 1) {
                    $error++;
                }
            } while ($error == 1);
            BaseModelCommon::debug("affected_num: " . $result['affected_num'], "Result", 1);
            return true;
		}
		else
		{
			return $this->error("更新数据有误，必须为一个有效数组！");
		}
	}

    /**
	 * date delete
	 * @param string|array $where
	 * @return void
	 */
	public function delete($where, &$result = array())
	{
		$this->checkLink("master");
		if (is_array($where))
		{
			$tmp = array();//条件，对应key=value
			foreach ($where as $key => $value)
			{
				$tmp[] = "`".$key."` = '".$this->link->real_escape_string($value)."'";
			}
			$where = implode(' AND ',$tmp);
		}
		else
		{
			$tmp_where = strtolower($where);
			if (!strpos ($tmp_where, "=") && !strpos ($tmp_where, 'in') && !strpos ($tmp_where, 'like'))
			{
				$this->error("条件错误");
			}
		}
		$this->setSql("DELETE FROM `".$this->getTableName()."` WHERE ".$where);
		//var_dump($this->sql);
        $error = 0;
        do {
            if ($error == 1) {
                $this->checkLink('master', true); 
            }
            $this->link->query($this->sql);
            $this->errorCode = $this->link->errno;
            $result['affected_num'] = $this->link->affected_rows;
            $this->errorCode = $this->link->errno;
            if (in_array($this->errorCode, self::$reConnectErrorArr)) {
                $error++;
            } elseif ($this->errorCode !== 0) {
                $this->errorInfo = $this->link->error;
                $this->error("执行删除语句出错\nSQL:".$this->sql."\nMYSQL错误代码:".$this->errorCode."\n错误信息:".$this->errorInfo);
            } elseif ($error == 1) {
                $error++;
            }
        } while ($error == 1);
        BaseModelCommon::debug("affected_num: " . $result['affected_num'], "Result", 1);
        return true;
	}

    /**
	 * exec a sql on master db
	 * @param string|array $where
	 * @return void
	 */
	public function exec($sql, &$result = array())
	{
		$this->checkLink("master");
		$this->setSql($sql);
        $error = 0;
        do {
            if ($error == 1) {
                $this->checkLink('master', true); 
            }
            $this->link->query($this->sql);
            $this->errorCode = $this->link->errno;
            $result['affected_num'] = $this->link->affected_rows;
            $this->errorCode = $this->link->errno;
            if (in_array($this->errorCode, self::$reConnectErrorArr)) {
                $error++;
            } elseif ($this->errorCode !== 0) {
                $this->errorInfo = $this->link->error;
                $this->error("SQL语句执行出错\nSQL:".$this->sql."\nMYSQL错误代码:".$this->errorCode."\n错误信息:".$this->errorInfo);
            } elseif ($error == 1) {
                $error++;
            }
        } while ($error == 1);
        BaseModelCommon::debug("affected_num: " . $result['affected_num'], "Result", 1);
        return true;
	}

    /**
     * error report
     * @param string $msg
     * @return void
     * @author wangxin3
     **/
    private function error($msg, $data = array())
    {
        if (in_array($this->errorInfo[1], $this->ignoreErrorArr)) {
            return false;
        }
        if (DB_ERROR_MOD === 0) {
            BaseModelMessage::showError($msg, $data);
        } elseif (DB_ERROR_MOD === 1) {
            echo $msg;
            return false;
        } else {
            return false;
        }
    }

    /**
	 * 输出sql语句
	 * @param string $sql
	 * @param array $data
	 * @return void
	 */
	protected function setSql($sql,$data = array())
	{
		if (is_array ($data) && !empty($data))
		{
			$sqlShow = "";
			$sqlArr = explode("?", $sql);
			foreach ($sqlArr as $k => $v)
			{
				if (!empty ($v))
				{
                    $value = isset($data[$k]) ? "'" . $this->link->real_escape_string($data[$k]) . "'" : "";
					$sqlShow .= $v . $value;
				}
			}
		}
		else
		{
			$sqlShow = $sql;
		}
		if (DEBUG == true)
		{
            BaseModelCommon::debug($sqlShow, 'SQL');
		}
		$this->sql = $sqlShow;
	}//end funtion

    /**
     * class destructor
     **/
	public function __destruct()
	{
		unset($this->tableName);
		unset($this->_master_or_slave);
		unset($this->sql);
		unset($this->errorCode);
		unset($this->errorInfo);
		unset($this->_db_id);
	}//end funtion
}
